package devcken.net.socket;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.StringUtils;

import devcken.runtime.EventHandler;
import devcken.utility.GuidUtil;

public class TcpListener
{
	/**
	 * 소켓 리스너를 나타낸다.
	 */
	private AsynchronousServerSocketChannel _listener;
	
	/**
	 * IP 주소를 나타낸다.
	 */
	private InetAddress _address;
	
	/**
	 * 포트 번호를 나탄낸다.
	 */
	private int _port;
	
	/**
	 * 허용된 클라이언트 사전을 나타낸다.
	 */
	private Map<String, TcpClient> _acceptedClients;
	
	/**
	 * 리스너가 수신 대기 상태인지 아닌지를 나타낸다.
	 */
	private boolean _isListening;
	
	/**
	 * 내부 헤더의 사용 여부를 가져온다.
	 */
	private boolean _useInternalHeader;
	
	/**
	 * TCP 리스너가 클라이언트를 수신할 수 있는 상태가 됐을 때 실행된다.
	 */
	public EventHandler<Void> listeningStarted;
	
	/**
	 * TCP 리스너가 클라이언트로부터 수신을 중지하고 포트 바인딩을 해제했을 때 실행된다.
	 */
	public EventHandler<Void> listeningStopped;
	
	/**
	 * 클라이언트의 접속이 허용됐을 때 실행된다.
	 */
	public EventHandler<TcpClient> acceptCompleted;
	
	/**
	 * 클라이언트의 접속이 끊어졌을 때 실행된다.
	 */
	public EventHandler<TcpClient> clientDisconnected;
	
	public TcpListener(int port, boolean useInternalHeader)
	{
		this(port, useInternalHeader, 0);
	}
	
	public TcpListener(int port, boolean useInternalHeader, int pieceBufferSize)
	{
		this._useInternalHeader = useInternalHeader;
		
		this._port = port;
		
		//this._pieceBufferSize = pieceBufferSize;
		
		this._acceptedClients = new HashMap<String, TcpClient>();
	}
	
	/**
	 * 
	 * @return
	 * @throws IOException
	 */
	public boolean listen() throws IOException
	{
		try
		{
			this._address = InetAddress.getLocalHost();
			
			this._listener = AsynchronousServerSocketChannel.open();
			
			InetSocketAddress socketAddress = new InetSocketAddress(this._address, this._port);
			
			this._listener.bind(socketAddress);
			
			this.acceptAsynchronous();
		}
		catch (Exception error)
		{
			throw error;
		}
		
		return false;
	}
	
	/**
	 * 소켓의 수신 상태를 닫는다.
	 * @throws IOException 소켓 리스너를 닫을 때 발생한다.
	 */
	public void close() throws IOException
	{
		try
		{
			if (!this._isListening || this._listener == null)
			{
				return;
			}
			
			Collection<TcpClient> clients = this._acceptedClients.values();
			
			for (TcpClient client : clients)
			{
				client.disconnectAsynchronous(false);
			}
			
			this._listener.close();
			
			this._isListening = false;
			
			this.listeningStopped.fire(null);
		}
		catch (Exception error)
		{
			throw error;
		}
	}
	
	/**
	 * 
	 */
	private void acceptAsynchronous()
	{
		this._listener.accept(this, new CompletionHandler<AsynchronousSocketChannel, TcpListener>()
		{
			@Override
			public void completed(AsynchronousSocketChannel socket, final TcpListener listener)
			{
				final ByteBuffer buffer = ByteBuffer.allocate(TcpClient._DEFAULT_BUFFER_SIZE);
				
				socket.read(buffer, socket, new CompletionHandler<Integer, AsynchronousSocketChannel>()
				{
					@Override
					public void completed(Integer receivedLength, AsynchronousSocketChannel socket)
					{
						byte[] received = buffer.array();
						
						AcceptCondition condition = null;
						
						String socketId = StringUtils.EMPTY;
						
						try
						{
							if (AcceptCondition.parse(received, condition))
							{
								socketId = condition.getId();
							}
							else
							{
								socketId = GuidUtil.generateGuid();
							}
							
							TcpClient client = new TcpClient(socketId, listener._useInternalHeader, socket, condition.getEndian(), TcpClient._DEFAULT_BUFFER_SIZE);
							
							client.ReceiveAsynchronous();
							
							listener._acceptedClients.put(socketId, client);
							
							listener.acceptCompleted.fire(client);
						}
						catch (IOException error)
						{
							
						}
						finally
						{
							if (listener._isListening)
							{
								listener.acceptAsynchronous();
							}
						}
					}

					@Override
					public void failed(Throwable error, AsynchronousSocketChannel socket)
					{
						
					}
				});
			}

			@Override
			public void failed(Throwable error, TcpListener listener)
			{
				
			}
		});
	}
}